#include "RequestHandlerFactory.h"
#include "DemoTcpServer.h"
#include "GlobalConfig.h"

//////////////////////////////////////////////////////////////////////////
#include "Poco/Net/HTTPServer.h"
#include "Poco/Net/HTTPRequestHandler.h"
#include "Poco/Net/HTTPRequestHandlerFactory.h"
#include "Poco/Net/HTTPServerParams.h"
#include "Poco/Net/HTTPServerRequest.h"
#include "Poco/Net/HTTPServerResponse.h"
#include "Poco/Net/HTTPServerParams.h"
#include "Poco/Net/ServerSocket.h"
#include "Poco/Net/WebSocket.h"
#include "Poco/Net/NetException.h"
#include "Poco/Util/ServerApplication.h"
#include "Poco/Util/Option.h"
#include "Poco/Util/OptionSet.h"
#include "Poco/Util/HelpFormatter.h"

using Poco::Net::ServerSocket;
using Poco::Net::WebSocket;
using Poco::Net::WebSocketException;
using Poco::Net::HTTPRequestHandler;
using Poco::Net::HTTPRequestHandlerFactory;
using Poco::Net::HTTPServer;
using Poco::Net::HTTPServerRequest;
using Poco::Net::HTTPResponse;
using Poco::Net::HTTPServerResponse;
using Poco::Net::HTTPServerParams;
using Poco::Timestamp;
using Poco::ThreadPool;
using Poco::Util::ServerApplication;
using Poco::Util::Application;
using Poco::Util::Option;
using Poco::Util::OptionSet;
using Poco::Util::HelpFormatter;
//////////////////////////////////////////////////////////////////////////

class ExternalSocketServer : public Poco::Util::ServerApplication
{
public:
	ExternalSocketServer() : _helpRequested(false)
	{
	}

	~ExternalSocketServer()
	{
	}

protected:
	void initialize(Application& self)
	{
		loadConfiguration(); // load default configuration files, if present
		ServerApplication::initialize(self);
	}

	void uninitialize()
	{
		ServerApplication::uninitialize();
	}

	void defineOptions(OptionSet& options)
	{
		ServerApplication::defineOptions(options);

		options.addOption(
			Option("help", "h", "display help information on command line arguments")
			.required(false)
			.repeatable(false));
	}

	void handleOption(const std::string& name, const std::string& value)
	{
		ServerApplication::handleOption(name, value);

		if (name == "help")
			_helpRequested = true;
	}

	void displayHelp()
	{
		HelpFormatter helpFormatter(options());
		helpFormatter.setCommand(commandName());
		helpFormatter.setUsage("OPTIONS");
		helpFormatter.setHeader("A sample HTTP server supporting the WebSocket protocol.");
		helpFormatter.format(std::cout);
	}

	void readConfig()
	{
		// get parameters from configuration file
		
		GlobalConfig::ServerConfig::instance().wsPort = (unsigned short)config().getInt("WebSocketServer.port", 9980);

		GlobalConfig::ServerConfig::instance().maxQueued = config().getInt("WebSocketServer.maxQueued", 100);
		GlobalConfig::ServerConfig::instance().maxThreads = config().getInt("WebSocketServer.maxThreads", 16);
		GlobalConfig::ServerConfig::instance().statusURI = config().getString("WebSocketServer.statusURI", "/status");

		GlobalConfig::ServerConfig::instance().docRoot = std::move(config().getString("WebSocketServer.docRoot", "/"));
		// get parameters from configuration file
		GlobalConfig::ServerConfig::instance().tcpPort = (unsigned short)config().getInt("TcpServer.port", 9977);
	}

	int main(const std::vector<std::string>& args)
	{

		if (_helpRequested)
		{
			displayHelp();
		}
		else
		{
			readConfig();

			std::vector<RequestHandlerDefinition> requestHandlers;
			bool ret = loadLibraryConfiguration(requestHandlers);
			if (!ret)
			{
				return Application::EXIT_CONFIG;
			}

			auto factory = new RequestHandlerFactory();
			factory->setRequestHandler(requestHandlers);

			auto *pParams = new HTTPServerParams;
			pParams->setMaxQueued(GlobalConfig::ServerConfig::instance().maxQueued);
			pParams->setMaxThreads(GlobalConfig::ServerConfig::instance().maxThreads);

			// set-up a server socket
			ServerSocket websvs(GlobalConfig::ServerConfig::instance().wsPort);
			// set-up a HTTPServer instance
			HTTPServer httpsrv(factory, websvs, pParams);
			// start the HTTPServer
			httpsrv.start();

		/////////////////////////////////////////////////////////////////////////////////

			// set-up a server socket
			ServerSocket tcpsvs(GlobalConfig::ServerConfig::instance().tcpPort);
			// set-up a SocketReactor...
			SocketReactor reactor;
			// ... and a SocketAcceptor
			SocketAcceptor<TcpServiceHandler> acceptor(tcpsvs, reactor);
			// run the reactor in its own thread so that we can wait for 
			// a termination request
			Thread thread;
			thread.start(reactor);
			// wait for CTRL-C or kill
			waitForTerminationRequest();

			// Stop the HTTPServer
			httpsrv.stop();

			// Stop the SocketReactor
			reactor.stop();
			thread.join();
		}
		return Application::EXIT_OK;
	}

private:
	bool _helpRequested;

	bool loadLibraryConfiguration(std::vector<RequestHandlerDefinition> &requestHandlers);
};


bool ExternalSocketServer::loadLibraryConfiguration(std::vector<RequestHandlerDefinition> &requestHandlers) {
	bool noErrors = true;

	const std::string keyPrefix = "WebSocketServer.library";
	const std::string keyClassLabel = "class";
	const std::string keyPathLabel = "fullPath";

	Poco::Util::AbstractConfiguration::Keys libraryConfigKeys;
	this->config().keys(keyPrefix, libraryConfigKeys);
	for (auto key : libraryConfigKeys)
	{
		std::string libraryKey = keyPrefix + "." + key ;
		std::string className = libraryKey + "." + keyClassLabel;
		std::string fullPath = libraryKey + "." + keyPathLabel;
		try 
		{
			const auto uriValue = this->config().getString(libraryKey);
			const auto classNameValue = this->config().getString(className);
			const auto pathValue = this->config().getString(fullPath);
			const RequestHandlerDefinition requestHandler = { uriValue, key, pathValue, Poco::SharedLibrary::suffix(), classNameValue };
			requestHandlers.push_back(requestHandler);
		}
		catch (Poco::NotFoundException ex) 
		{
			noErrors = false;
			//l.information("Configuration for library " + key + " is not complete. Server is not started.");
		}
	}
	return noErrors;
}
int main(int argc, char** argv)
{
	ExternalSocketServer app;
	return app.run(argc, argv);
}
